#include <game/controls/ControlCollectionLoader.h>
#include <game/controls/Control.h>
#include <game/controls/ControlFactory.h>
#include <game/ResourceReferenceDefinition.h>

#include <Object2D.h>
#include <Gluck.h>
#include <xml/Object.h>
#include <xml/Stream.h>
#include <lib3d/node/Node.h>
#include <AutoContainer.h>
#include <resource/Manager.h>
#include <game/XMLProperties.h>

#include <logs/Log.h>

using namespace liba::lib3d;
namespace {
const char* LOGGER="ControlLoader";
} // anonymous namespace 

ARELLO_GAME_NAMESPACE_DECL_START
namespace controls {

class ControlLoader
	: public xml::ObjectANSI
{
private:
	virtual bool on_attribute(xml::Provider * prov, const std::string & name, const std::string & value);
	virtual bool on_node(xml::Provider * prov, const std::string & name);
public:
	ControlLoader(const std::string& className, node::Node* root)
		: className(className)
		, root(root)
		, factory(0)
	{ 
		if (!root)
			THROW(Gluck(__FUNCTION__, "Root node undefined while creating control loader"));
	}

	void SetFactory(const ControlFactory* value) { this->factory = value; }

	boost::shared_ptr<Control> GetControl() 
	{ 
		if (!this->control.get())
			createControl();
		return control; 
	}
private:
	void createControl()
	{
		if (this->control.get())
			return;

		if (this->node.empty())
		{
			LOG_FATAL(LOGGER, "Node is not set for control \"%s\"", this->name.c_str());
			THROW(Gluck(__FUNCTION__, "Node is not set for control"));
		}

		node::Node* n = this->root->FindNode(Atom(this->node), true);
		if (!n)
		{
			LOG_FATAL(LOGGER, "Failed to find node \"%s\" for control \"%s\"", this->node.c_str(), this->name.c_str());
			THROW(Gluck(__FUNCTION__, "Failed to find node for control"));
		}

		liba::Object2D* o2d = dynamic_cast<liba::Object2D*>(n);
		if (!o2d)
		{
			LOG_FATAL(LOGGER, "Failed to cast node \"%s\" for control \"%s\" to Object2D", this->node.c_str(), this->name.c_str());
			THROW(Gluck(__FUNCTION__, "Failed to cast node for control"));
		}

		this->control = factory->Create(className, Atom(this->name), o2d);
	}

	boost::shared_ptr<Control> control;
	
	std::string className;
	std::string name;
	std::string node;
	node::Node* root;
	const ControlFactory* factory;
};

bool ControlLoader::on_attribute(xml::Provider * prov, const std::string & name, const std::string & value)
{
	if (name == "Name")
		return converter::convert(value, &this->name);
	if (name == "Node")
		return converter::convert(value, &this->node);

	return false;
}

bool ControlLoader::on_node(xml::Provider * prov, const std::string & name)
{
	createControl();

	if ("Properties" == name)
	{
		Properties properties;
		bool res = prov->parse_object(&properties);
		control->SetProperties(properties);
		return res;
	}
	else
	{
		if (control.get() && control->GetLoader())
			return control->GetLoader()->on_node(prov, name);
	}

	return false;
}

struct ControlCollectionLoaderPrivates
	: public xml::ObjectANSI
{
	ControlCollectionLoaderPrivates()
		: root(0)
	{ }

	virtual bool on_node(xml::Provider * prov, const std::string & name );

	node::Node* root;
	const ControlFactory* factory;
	std::vector<boost::shared_ptr<Control> > controls;
};


bool ControlCollectionLoaderPrivates::on_node(xml::Provider * prov, const std::string & name )
{
	if (name == "Resource")
	{
		ResourceReferenceDefinition refDef;
		if (!prov->parse_object(refDef.GetLoader()))
			return false;

		ControlCollectionLoaderPrivates ldr;
		ldr.root = this->root;
		ldr.factory = this->factory;
		std::auto_ptr<resource::Resource> scene_res(resource::Manager::create_resource(refDef.GetPath()));
		if (!xml::StreamResource(scene_res.get()).load(&xml::Root(&ldr, "Controls")))
			THROW(GluckBadResource(__FUNCTION__, "load() failed", refDef.GetPath()));

		this->controls.insert(this->controls.end(), ldr.controls.begin(), ldr.controls.end());
		ldr.controls.clear();

		return true;
	}
	if (this->factory->IsValidControlName(name))
	{
		ControlLoader ldr(name, this->root);
		ldr.SetFactory(this->factory);

		if (!prov->parse_object(&ldr))
			return false;
		
		controls.push_back(ldr.GetControl());
		return true;
	}

	return false;
}

ControlCollectionLoader::ControlCollectionLoader()
	: privates(new ControlCollectionLoaderPrivates())
{
}

ControlCollectionLoader::~ControlCollectionLoader()
{
}

liba::xml::ObjectANSI* ControlCollectionLoader::GetLoader()
{
	return privates.get();
}

void ControlCollectionLoader::SetRootNode(node::Node* root)
{
	privates->root = root;
}

void ControlCollectionLoader::SetControlFactory(const ControlFactory* value)
{
	privates->factory = value;
}

const std::vector<boost::shared_ptr<Control> >& ControlCollectionLoader::GetLoadedControls()
{
	return privates->controls;
}

} // namespace controls
ARELLO_GAME_NAMESPACE_DECL_END